package cn.lyf.tools;

import java.util.*;
import java.io.*;
import java.nio.file.*;
import java.util.function.*;
import java.util.regex.*;

//awk [-参数 变量] 'BEGIN{初始化}条件类型1{动作1}条件类型2{动作2}。。。。END{后处理}'
//{ }类似一个循环体，会对文件中的每一行进行迭代
public class AWK {
    AWK context = this;
    public boolean debug = false;
    public Integer OFW = null;

    public AWK() {
        init();
    }

    public Integer NF, NR, FNR, RSTART, RLENGTH;
    public String FS, RS, FILENAME, ERRNO;
    public String OFS, ORS, FPAT, FIELDWIDTHS;
    public List<String> $, groups;
    public Consumer<AWK> BEGIN = a -> {
    }, END = a -> {
    };
    public List<statement> stmts = new ArrayList<>();
    // 系统环境变量
    public Map<String, String> ENVIRON = System.getenv();
    // 挂载外部变量
    public Map<String, Object> VAR = new HashMap<>();
    // 处理后的结果
    public List<String> RES;

    public void setVAR(String varname, Object val) {
        VAR.put(varname, val);
    }

    public void init() {
        FS = " ";
        OFS = "\t";
        RS = ORS = "\n";
        FILENAME = FIELDWIDTHS = FPAT = ERRNO = null;
        NF = NR = FNR = 0;
        RSTART = RLENGTH = null;
        $ = groups = null;
        RES = new ArrayList<String>();
    }

    boolean splitByWidth(String str) {
        if (FIELDWIDTHS == null)
            return false;
        Integer[] ws = Arrays.stream(FIELDWIDTHS.trim().split(" ")).map(Integer::parseInt).toArray(Integer[]::new);
        for (int i = 0, j = 0; i < ws.length; i++) {
            $.add(str.substring(j, j + ws[i]));
        }
        return true;
    }

    boolean splitByFPAT(String str) {
        if (FPAT == null)
            return false;
        Matcher m = Pattern.compile(FPAT).matcher(str);
        while (m.find()) {
            $.add(str.substring(m.start(), m.end()));
        }
        return true;
    }

    boolean splitByFS(String str) {
        if (FS == null)
            return false;
        String regex = "(" + FS + ")+";
        String[] parts=str.split(regex);
        if(!"".equals(parts[0]))$.add(parts[0]);
        for(int i=1;i<parts.length;i++){
            $.add(parts[i]);
        }
        return true;
    }

    public int getline() {
        try {
            if (it.hasNext()) {
                String row = it.next();
                $ = new ArrayList<>();
                $.add(row);
                if (!(splitByFS(row) || splitByWidth(row) || splitByFPAT(row))) {
                    throw new RuntimeException();
                }
                NF = $.size() - 1;
                return 1;
            }
            return 0;
        } catch (Exception e) {
            ERRNO = e.getMessage();
            e.printStackTrace();
        }
        return -1;
    }

    private class Iter implements Iterator<String> {
        Iterator<Map.Entry<String, List<String>>> iterator;
        Map.Entry<String, List<String>> cur;
        int index = 0;

        void setIterator(Iterator<Map.Entry<String, List<String>>> iterator) {
            this.iterator = iterator;
            cur = iterator.next();
            index = 0;
            FNR = 0;
            FILENAME = cur.getKey();
        }

        @Override
        public boolean hasNext() {
            return cur.getValue().size() > index || iterator.hasNext();
        }

        @Override
        public String next() {
            List<String> val = cur.getValue();
            if (val.size() == index && iterator.hasNext()) {
                setIterator(iterator);
                return next();
            }
            NR++;
            return val.get(index++);
        }

    }

    Iter it = new Iter();

    public static Predicate<AWK> match(String regex) {
        final Pattern p = Pattern.compile(regex);
        return ctx -> {
            Matcher m = p.matcher(ctx.$.get(0));
            if (m.find()) {
                int n = m.groupCount();
                ctx.groups = new ArrayList<String>();
                for (int i = 0; i <= n; i++)
                    ctx.groups.add(m.group(i));
                ctx.RSTART = m.start(0);
                ctx.RLENGTH = ctx.groups.get(0).length();
                return true;
            }
            return false;
        };
    }

    public static Predicate<AWK> match(String start, String end) {
        final Box<Boolean> state = new Box<>(Boolean.FALSE);
        final Pattern p0 = Pattern.compile(start);
        final Pattern p1 = Pattern.compile(end);
        return ctx -> state.v = (state.v ? !p1.matcher(ctx.$.get(0)).find() : p0.matcher(ctx.$.get(0)).find());
    }

    private static class Box<T> {
        T v;

        Box(T a) {
            v = a;
        }
    }

    public void doScript() {
        BEGIN.accept(context);
        while (true) {
            int a = getline();
            if (a != 1)
                break;
            try {
                for (statement stmt : stmts) {
                    stmt.doStatement();
                    if ($ == null)
                        break;
                }
            } catch (JumpAction jump) {
            } catch (EndAction e) {
                break;
            }
            if ($ != null)
                RES.add(getRecord($));

        }
        END.accept(context);
    }

    String getRecord(List<String> rs) {
        if (rs == null || rs.isEmpty())
            return "";
        StringBuffer sb = new StringBuffer();
        if (OFW != null) {
            String fmt = "%" + OFW + "s";
            sb.append(String.format(fmt, rs.get(1)));
            for (int i = 2; i < rs.size(); i++) {
                sb.append(String.format(fmt, rs.get(i)));
            }
            sb.append(ORS);
            return sb.toString();
        }
        sb.append(rs.get(1));
        for (int i = 2; i < rs.size(); i++) {
            sb.append(OFS).append(rs.get(i));
        }
        sb.append(ORS);
        return sb.toString();
    }

    private class statement {
        Predicate<AWK> pattern;
        Consumer<AWK> action;

        statement(Predicate<AWK> pattern, Consumer<AWK> action) {
            this.pattern = pattern;
            this.action = action;
        }

        void doStatement() {
            try {
                if (pattern.test(context)) {
                    action.accept(context);
                }
            } catch (IndexOutOfBoundsException | NumberFormatException e) {
                ERRNO = "warn:" + e.getMessage();
                if (debug)
                    System.err.println(ERRNO);
            }
        }
    }

    private class JumpAction extends RuntimeException {
    }

    private class EndAction extends RuntimeException {
    }

    // 用于退出当前记录
    public void next() {
        throw new JumpAction();
    }

    public void exit() {
        throw new EndAction();
    }

    public void addStatement(Predicate<AWK> pattern, Consumer<AWK> action) {
        stmts.add(new statement(pattern, action));
    }

    public void addStatement(Consumer<AWK> action) {
        stmts.add(new statement(a -> true, action));
    }

    public void addStatement(Predicate<AWK> pattern) {
        stmts.add(new statement(pattern, a -> {
            a.$ = null;
        }));
    }

    public void setFiles(Path[] Paths) {
        Map<String, List<String>> map = new HashMap<>();
        for (Path filePath : Paths) {
            map.put(filePath.toString(), Arrays.asList(readFileContent(filePath).split(RS)));
        }
        it.setIterator(map.entrySet().iterator());
    }

    public void setData(String str) {
        Map<String, List<String>> map = new HashMap<>();
        map.put(null, Arrays.asList(str.split(RS)));
        it.setIterator(map.entrySet().iterator());
    }

    private String readFileContent(Path path) {
        StringBuffer sb = new StringBuffer();
        String tempStr;
        try (BufferedReader reader = new BufferedReader(new FileReader(path.toFile()));) {
            while ((tempStr = reader.readLine()) != null)
                sb.append(tempStr).append('\n');
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

}
